﻿namespace Typps
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Data;
    using System.Drawing.Design;
    using System.IO;
    using System.Linq;
    using System.Resources;
    using System.Text;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    public abstract class ControlBase : WebControl, INamingContainer, ICompositeControlDesignerAccessor, IScriptControl, ITheming, IControlBase
    {
        #region Fields

        string _callbackArgument = string.Empty;
        bool _formatCss = false;
        string[] _manifestResourceNamesCache;
        string _rootImageSkinFolder = "Typps.Resources.Skins.{0}.Images.Editor.{1}";
        string _rootSkinFolder = "Typps.Resources.Skins.{0}.{1}";
        string _rootTemplateSkinFolder = "Typps.Resources.Skins.{0}.Templates.{1}";

        #endregion Fields

        #region Constructors

        protected ControlBase()
        {
            _manifestResourceNamesCache = GetType().Assembly.GetManifestResourceNames();
            RM = Properties.Resources.ResourceManager;
        }

        protected ControlBase(string tag)
            : base(tag)
        {
            _manifestResourceNamesCache = GetType().Assembly.GetManifestResourceNames();
            RM = Properties.Resources.ResourceManager;
        }

        #endregion Constructors

        #region Events

        internal event EventHandler SelectedSkinChanged;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets a ClientScriptManager object used to manage, register, and add script to the page
        /// </summary>
        [Browsable(false)]
        public virtual ClientScriptManager ClientScript
        {
            get
            {
                return (Page != null) ? Page.ClientScript : null;
            }
        }

        [Browsable(false)]
        public virtual Type ComponentType
        {
            get
            {
                return this.GetType();
            }
        }

        // Properties
        public override ControlCollection Controls
        {
            get
            {
                this.EnsureChildControls();
                return base.Controls;
            }
        }

        /// <summary>
        /// The order in which this property is set matters greatly. You must
        /// first set CustomSkinFolder and then try and set this property, since
        /// it is a dependant property. 
        /// </summary>
        [TypeConverter(typeof(SkinConverter)),
        DefaultValue("None"),
        RefreshProperties(RefreshProperties.Repaint)]
        public virtual string CustomSkin
        {
            get { return (string)(ViewState["CustomSkin"] ?? "None"); }
            set
            {
                ViewState["CustomSkin"] = value;
                if (DesignMode)
                {
                    ResetCollectionsAndStyles();
                    OnSelectedSkinChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// The folder containing your custom skin. Please note that you 
        /// have to follow a pattern when creating custom skins
        /// </summary>
        [Editor(typeof(FolderNameBrowser), typeof(UITypeEditor)),
        DefaultValue("")]
        public virtual string CustomSkinFolder
        {
            get
            {
                return (string)(ViewState["CustomSkinFolder"] ?? string.Empty);
            }
            set
            {
                ViewState["CustomSkinFolder"] = value;
                if (DesignMode)
                {
                    CustomSkin = "None";
                    ResetCollectionsAndStyles();
                }
            }
        }

        [Browsable(false)]
        public new bool DesignMode
        {
            get
            {
                return (HttpContext.Current == null);
            }
        }

        /// <summary>
        /// If you supply an external css file, this control will use that instead
        /// of referencing the css embedded in this control as a resource.
        /// Before including an external stylesheet you may use the "Get autogenerated css" 
        /// option exposed by this control in design mode within the vs.net designer 
        /// via the SmartTag and then modify the css and reference it
        /// as an external stylesheet using this property.
        /// </summary>
        [DefaultValue("")]
        [Editor(typeof(System.Web.UI.Design.UrlEditor), typeof(UITypeEditor))]
        public virtual string ExternalStyleSheet
        {
            get
            {
                string url = (string)(ViewState["ExternalStyleSheet"] ?? string.Empty);
                if (url != string.Empty && !DesignMode)
                    return ResolveUrl(url);
                return url;
            }
            set
            {
                ViewState["ExternalStyleSheet"] = value;
            }
        }

        [Browsable(false)]
        public virtual bool IsCallback
        {
            get
            {
                if (DesignMode || Context == null) return false;
                string key = string.Format("iscallback${0}", UniqueID);
                string value = Context.Request.Form[key];
                return (!string.IsNullOrEmpty(value));
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual bool IsCustomSkin
        {
            get
            {
                return (!string.IsNullOrEmpty(CustomSkinFolder) &&
                            CustomSkin != "None");
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual bool IsDesignMode
        {
            get
            {
                return DesignMode;
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual string[] ManifestResourceNamesCache
        {
            get
            {
                return _manifestResourceNamesCache;
            }
        }

        /// <summary>
        /// when nesting the editor inside a templated control, it's very possible
        /// that the template is enabled based on a postback. Eg, if you nested
        /// the editor in the Edit template of a DetailsView, and click a toolBar
        /// button on the editor that does an async postback, you may need to 
        /// tell your serverside code to rebuild itself in edit mode. In order to do
        /// this, we also need to postback additional information, more specifically
        /// values contained in the form element that have nothing to do with the editor. 
        /// </summary>
        [DefaultValue(false)]
        public virtual bool PostbackAllFormData
        {
            get
            {
                return (ViewState["PostbackAllFormData"] == null) ?
                    false : (bool)ViewState["PostbackAllFormData"];
            }
            set
            {
                ViewState["PostbackAllFormData"] = value;
            }
        }

        /// <summary>
        /// Not for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual string SelectedSkin
        {
            get
            {
                if (!string.IsNullOrEmpty(CustomSkin) &&
                    !CustomSkin.Equals("None"))
                    return CustomSkin;
                return Skin.ToString();
            }
        }

        /// <summary>
        /// Gets/Sets a skin. Builtin Skins available to the 
        /// control are contained within the control as resources. 
        /// </summary>
        [DefaultValue(typeof(SkinNames), "Classic"),
        RefreshProperties(RefreshProperties.Repaint)]
        public virtual SkinNames Skin
        {
            get
            {
                object o = (object)ViewState["Skin"];
                if (o == null)
                    o = SkinNames.Classic;
                return (SkinNames)o;
            }
            set
            {
                ViewState["Skin"] = value;
                if (DesignMode)
                {
                    ResetCollectionsAndStyles();
                    OnSelectedSkinChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// When this value is true, it will make use of the default HttpBrowserCapabilities
        /// classs to detect if it's in uplevel mode or not. You would be setting this to
        /// true if you already defined some proper browsercaps section in your web.config,
        /// otherwise don't bother. By default the control will try to parse the useragent
        /// manually and retrieve browsername/version number. This is done via the UserAgent class.
        /// </summary>
        [DefaultValue(false),
        Description("When true, uses default HttpBrowserCapabilities")]
        public bool UseBrowserCaps
        {
            get
            {
                return (bool)(ViewState["UseBrowserCaps"] ?? false);
            }
            set
            {
                ViewState["UseBrowserCaps"] = value;
            }
        }

        /// <summary>
        /// Gets/Sets a value indicating whether the CSS code generated by the editor maintains 
        /// readable formatting. The default is false, which means all spaces are removed to maintain a small
        /// css file footprint
        /// </summary>
        [DefaultValue(false)]
        internal virtual bool FormatCss
        {
            get
            {
                return _formatCss;
            }
            set
            {
                _formatCss = value;
            }
        }

        internal ResourceManager RM
        {
            get; set;
        }

        protected virtual string CallbackArgument
        {
            get
            {
                if (string.IsNullOrEmpty(_callbackArgument)&&!DesignMode)
                {
                    string key = string.Format("formdata${0}", UniqueID);
                    string value = Context.Request.Form[key];
                    if (!string.IsNullOrEmpty(value))
                        return value;
                }
                return _callbackArgument;
            }
            set
            {
                _callbackArgument = value;
            }
        }

        protected virtual string ClassName
        {
            get
            {
                if (this.CssClass == string.Empty)
                    return this.ClientID;
                return this.CssClass;
            }
        }

        protected abstract string CssKey
        {
            get;
        }

        protected virtual IDesignerHost Host
        {
            get
            {
                IDesignerHost host = null;
                if (DesignMode)
                    host = (IDesignerHost)this.Site.GetService(typeof(IDesignerHost));
                return host;
            }
        }

        [DefaultValue(true),
        Browsable(false)]
        protected virtual bool IsBrowserUpLevel
        {
            get
            {
                if (ViewState["IsBrowserUpLevel"] == null)
                    return (DesignMode) ? true : Helper.IsUpLevel(UseBrowserCaps);
                return (bool)ViewState["IsBrowserUpLevel"];
            }
            set
            {
                ViewState["IsBrowserUpLevel"] = value;
            }
        }

        protected virtual bool IsIE6AndBelow
        {
            get
            {
                if (IsDesignMode) return false;
                HttpBrowserCapabilities browser = HttpContext.Current.Request.Browser;
                string name = browser.Browser.ToLower();
                float version = (float)(browser.MajorVersion + browser.MinorVersion);
                return (name.Equals("ie") && version <= 6);
            }
        }

        protected abstract string KeyFormat
        {
            get;
        }

        protected abstract bool RenderSharedStyles
        {
            get;
        }

        protected abstract bool RenderTreeViewStyles
        {
            get;
        }

        protected virtual string RootImageSkinFolder
        {
            get { return _rootImageSkinFolder; }
        }

        protected virtual string RootSkinFolder
        {
            get { return _rootSkinFolder; }
        }

        protected virtual string RootTemplateSkinFolder
        {
            get { return _rootTemplateSkinFolder; }
        }

        protected ScriptManager SM
        {
            get; set;
        }

        protected abstract string StartupCssStyleIdentifier
        {
            get;
        }

        protected abstract string StyleSheetName
        {
            get;
        }

        protected bool SupportsStyleSheet
        {
            get
            {
                if ((((this.Page.Header != null)
                    && (this.Page.Header.StyleSheet != null))
                    && (Context.Request.Browser != null))
                    && (((Context.Request.Browser["preferredRenderingType"] != "xhtml-mp")
                    && Context.Request.Browser.SupportsCss)
                    && !this.Page.IsCallback))
                {
                    return true;
                }
                else
                { return false; }
            }
        }

        protected virtual string TransformationFileName
        {
            get { return "Preconfig.transformations.xml"; }
        }

        #endregion Properties

        #region Methods

        public override void DataBind()
        {
            this.OnDataBinding(EventArgs.Empty);
            this.EnsureChildControls();
            this.DataBindChildren();
        }

        public virtual T FromJson<T>(string input)
        {
            return Helper.FromJson<T>(input);
        }

        public virtual string GetCssStyle()
        {
            return GetCssStyle(false);
        }

        public virtual string GetCssStyle(bool useDesigntimeResourceUrl)
        {
            string css = string.Empty;
            if (string.IsNullOrEmpty(ExternalStyleSheet))
            {
                string url = string.Format(
                    RootSkinFolder, SelectedSkin, IsCustomSkin ? Helper.ConvertToPhysicalPath(StyleSheetName) : StyleSheetName);

                css = GetTemplateData(url);

                css = TransformCssImageUrls(css, useDesigntimeResourceUrl);
            }
            else if (DesignMode)
            {
                string path = Helper.MapPath(Host, ExternalStyleSheet, PathType.Physical);
                using (StreamReader sr = new StreamReader(path))
                    css = sr.ReadToEnd();
            }
            return css;
        }

        public virtual string GetSharedCssStyle()
        {
            string url = string.Format(
                RootSkinFolder, SelectedSkin, IsCustomSkin ? "Css/shared.css" : "Css.shared.css");
            if (IsCustomSkin)
                url = Helper.GetPhysicalPathByResourceNamespace(url, this);

            string css = GetTemplateData(url);

            return TransformCssImageUrls(css, true);
        }

        public virtual string GetTreeViewCssStyle()
        {
            string url = string.Format(
                RootSkinFolder, SelectedSkin, IsCustomSkin ? "Css/treeview.css" : "Css.treeview.css");
            if (IsCustomSkin)
                url = Helper.GetPhysicalPathByResourceNamespace(url, this);

            string css = GetTemplateData(url);

            return TransformCssImageUrls(css, true);
        }

        void ICompositeControlDesignerAccessor.RecreateChildControls()
        {
            this.RecreateChildControls();
        }

        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            return GetScriptDescriptors();
        }

        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            return GetScriptReferences();
        }

        public virtual string ToJsonString<T>(T input)
        {
            return Helper.ToJsonString<T>(input);
        }

        protected void EnsureScriptManager()
        {
            if (SM == null)
                SM = ScriptManager.GetCurrent(Page);
        }

        protected virtual string GetLocalizedText(string key)
        {
            return Helper.GetGlobalResourceObject(RM, string.Format(KeyFormat, key));
        }

        protected abstract IEnumerable<ScriptDescriptor> GetScriptDescriptors();

        protected abstract IEnumerable<ScriptReference> GetScriptReferences();

        protected virtual string GetTemplateData(string templateName)
        {
            Type t = typeof(Editor);
            string template = Helper.GetTemplateData(templateName,
                true, t, _manifestResourceNamesCache, RM, ClassName, ClientID);

            return template;
        }

        protected virtual DataSet GetTemplateFromResource(string templateName)
        {
            DataSet ds = null;
            string resourceName = (string)(Helper.GetManifestResourceName(templateName,
                    _manifestResourceNamesCache) ?? string.Empty);

            if (IsCustomSkin)
            {
                if (DesignMode && Host != null)
                    resourceName = Helper.MapPath(Host, templateName, PathType.Physical);
                else
                    resourceName = Context.Server.MapPath(templateName);
                ds = new DataSet();
                ds.ReadXml(resourceName);
            }
            else if (!string.IsNullOrEmpty(resourceName))
            {
                ds = new DataSet();
                using (Stream s = GetType().Assembly.GetManifestResourceStream(
                       resourceName))
                    ds.ReadXml(s);
            }
            return ds;
        }

        protected virtual void InitializeCSSRegistration()
        {
            if (this.DesignMode) return;

            if (!SM.IsInAsyncPostBack && string.IsNullOrEmpty(ExternalStyleSheet))
            {
                if (RenderTreeViewStyles)
                    Helper.RegisterSharedExternalStyleSheetReferences(this.Page,
                        QueryStringData(CssFile.Shared), QueryStringData(CssFile.TreeView));
                if (RenderSharedStyles)
                    Helper.RegisterSharedExternalStyleSheetReferences(this.Page,
                    QueryStringData(CssFile.Shared));
            }
            string id = StartupCssStyleIdentifier;
            Control c = Page.Header.FindControl(id);
            if (c != null) return;
            if (!string.IsNullOrEmpty(ExternalStyleSheet))
            {
                if (ExternalStyleSheet.IndexOf(@"App_Themes/",
                            StringComparison.OrdinalIgnoreCase) == -1)
                {
                    string link = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"{0}\" />";
                    Literal l = new Literal();
                    l.EnableViewState = false;
                    l.ID = id;
                    l.Text = string.Format(link, ResolveUrl(ExternalStyleSheet));
                    Page.Header.Controls.Add(l);
                }
            }
            else if (this.Visible)
            {
                RegisterEmbededCss();
            }
        }

        protected virtual void OnSelectedSkinChanged(EventArgs e)
        {
            if (SelectedSkinChanged != null)
                SelectedSkinChanged(this, e);
        }

        protected abstract string QueryStringData(CssFile cssFileToRegister);

        protected virtual void RecreateChildControls()
        {
            base.ChildControlsCreated = false;
            this.EnsureChildControls();
        }

        protected virtual void RegisterEmbededCss()
        {
            EnsureScriptManager();
            //Is this really necessary?
            //do we really have to deposit the css directly vs linking to the stylesheet
            if (SM != null && SM.IsInAsyncPostBack)
            {
                string css = GetCssStyle();

                if (!FormatCss)
                    css = Helper.TrimWhiteSpace(css);

                if (!Helper.IsClientScriptBlockRegistered(CssKey, SM))
                {
                    //js doesn't supported line breaks in string literals
                    string styleBlock = Helper.GetStyleRegistrationByScriptBlock(css);
                    ScriptManager.RegisterClientScriptBlock(this, typeof(Editor), CssKey, styleBlock, true);
                }
                if (RenderSharedStyles)
                {
                    string sharedCssKey = "Typps.SharedCss";
                    if (!Helper.IsClientScriptBlockRegistered(sharedCssKey, SM))
                    {
                        string styleBlock = Helper.GetStyleRegistrationByScriptBlock(GetSharedCssStyle());
                        ScriptManager.RegisterClientScriptBlock(this, typeof(ResourceDummy), sharedCssKey, styleBlock, true);
                    }
                }
                if (RenderTreeViewStyles)
                {
                    string sharedTreeViewCssKey = "Typps.TreeViewCss";
                    if (!Helper.IsClientScriptBlockRegistered(sharedTreeViewCssKey, SM))
                    {
                        string styleBlock = Helper.GetStyleRegistrationByScriptBlock(GetTreeViewCssStyle());
                        ScriptManager.RegisterClientScriptBlock(this, typeof(ResourceDummy), sharedTreeViewCssKey, styleBlock, true);
                    }
                }
            }
            else
            {
                RegisterExternalStyleSheetReferences();
            }
        }

        protected virtual void RegisterExternalStyleSheetReferences()
        {
            string id = StartupCssStyleIdentifier;
            string url = Helper.GetEmbeddedWebResourceUrl(typeof(Editor), "css", true);
            string link = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"{0}\" />";
            string href = string.Format("~/{0}{1}", url, QueryStringData(CssFile.Auto));
            Literal l1 = new Literal();
            l1.EnableViewState = false;
            l1.ID = id;
            l1.Text = string.Format(link, Helper.HashQueryString(ResolveUrl(href)));
            Page.Header.Controls.Add(l1);
        }

        protected virtual void RenderStylesAndScripts()
        {
            if (SM == null)
                throw new HttpException("A ScriptManager control must exist on the current page.");

            if (!this.DesignMode && !IsBrowserUpLevel)
            {
                InitializeCSSRegistration(); return;
            }
            else if (DesignMode)
            {
                this.EnsureChildControls();
            }
            else
            {
                EnsureScriptManager();
                if (SM != null && SM.SupportsPartialRendering)
                {
                    SM.RegisterScriptControl(this);
                    SM.RegisterScriptDescriptors(this);
                }
                InitializeCSSRegistration();
            }
        }

        protected abstract void ResetCollectionsAndStyles();

        protected virtual void ResponseWrite(string message, string contentType)
        {
            Context.Response.ClearContent();
            Context.Response.ContentType = contentType;
            Context.Response.Write(message);
        }

        protected virtual string TransformCssImageUrls(string css, bool useDesigntimeResourceUrl)
        {
            string fileName = string.Format(
                RootSkinFolder, SelectedSkin, TransformationFileName);
            if (IsCustomSkin)
                fileName = Helper.ConvertToPhysicalPath(fileName);

            using (DataSet ds = GetTemplateFromResource(fileName))
            {
                foreach (DataRow dr in ds.Tables[0].Rows)
                {
                    string resourceName = Helper.GetStringProperty("resource", dr);
                    string url = resourceName;
                    if (IsCustomSkin)
                        url = Helper.ConvertToPhysicalPath(resourceName);
                    string consumableUrl = Helper.GetBackgroundImageUrl(this,
                        string.Format(RootSkinFolder, SelectedSkin, url), useDesigntimeResourceUrl);
                    css = css.Replace(resourceName, consumableUrl);
                }
            }
            return css;
        }

        #endregion Methods
    }
}